import os
import re
import json
import sys
import logging
import mimetypes
import smtplib
import threading
from email import encoders
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.header import Header
import tkinter as tk
from tkinter import filedialog, messagebox, Scrollbar, ttk

# 常量
EMAIL_REGEX = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
if getattr(sys, 'frozen', False):  # 判断是否为 EXE 运行环境
    BASE_DIR = os.path.dirname(sys.executable)  # 获取 EXE 所在目录
else:
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # Python 运行环境
CONFIG_FILE = os.path.join(BASE_DIR, "conf", "email_config.json")
LOG_FILE = os.path.join(BASE_DIR, "log", "email_log.log")  # 修改扩展名为 .log
SMTP_SERVERS = {
    "qq.com": "smtp.qq.com",
    "gmail.com": "smtp.gmail.com",
    "163.com": "smtp.163.com",
    "126.com": "smtp.126.com",
    "outlook.com": "smtp.office365.com",
    "hotmail.com": "smtp.office365.com",
    "yahoo.com": "smtp.mail.yahoo.com",
    "foxmail.com": "smtp.foxmail.com",
    "aliyun.com": "smtp.aliyun.com"
}

# 确保所需的目录和文件存在
def ensure_directories_and_files():
    if not os.path.exists("conf"):
        os.makedirs("conf")
        print("创建目录: conf")

    if not os.path.exists("log"):
        os.makedirs("log")
        print("创建目录: log")

    if not os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE, "w") as config_file:
            config_file.write("{}")
        print(f"创建文件: {CONFIG_FILE}")

    if not os.path.exists(LOG_FILE):
        with open(LOG_FILE, "w") as log_file:
            log_file.write("")
        print(f"创建文件: {LOG_FILE}")

ensure_directories_and_files()

# 自定义异常
class InvalidEmailFormatError(Exception):
    pass

# 日志配置
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s -[ %(funcName)s ]- %(message)s",
    encoding="utf-8"
)

# 获取 SMTP 服务器地址
def get_smtp_server(email):
    if not re.match(EMAIL_REGEX, email):
        logging.error("无效的邮箱格式: %s", email)
        raise InvalidEmailFormatError(f"无效的邮箱地址: {email}")
    domain = email.split("@")[-1].lower()
    smtp_server = SMTP_SERVERS.get(domain)
    if not smtp_server:
        logging.warning("未知的邮箱域名: %s", domain)
        raise ValueError(f"不支持的邮箱域名: {domain}")
    return smtp_server

# 保存配置
def save_config(sender_email, sender_password, recipient_email):
    try:
        with open(CONFIG_FILE, "w") as file:
            json.dump({"sender_email": sender_email, "sender_password": sender_password, "recipient_email": recipient_email}, file)
    except Exception as e:
        logging.error(f"保存配置失败: {e}")

# 加载配置
def load_config():
    if os.path.exists(CONFIG_FILE):
        try:
            with open(CONFIG_FILE, "r") as file:
                return json.load(file)
        except Exception as e:
            logging.error(f"加载配置失败: {e}")
    return {}

# 判断附件是否过大
def is_attachment_too_large(attachment_path, max_size_mb=25):
    return os.path.getsize(attachment_path) > max_size_mb * 1024 * 1024

# 分块添加附件
def attach_file_in_chunks(msg, attachment_path, chunk_size=5 * 1024 * 1024):
    mime_type, _ = mimetypes.guess_type(attachment_path)
    mime_type = mime_type or 'application/octet-stream'
    main_type, sub_type = mime_type.split('/')
    with open(attachment_path, 'rb') as attachment_file:
        part_number = 1
        while True:
            chunk = attachment_file.read(chunk_size)
            if not chunk:
                break
            part = MIMEBase(main_type, sub_type)
            part.set_payload(chunk)
            encoders.encode_base64(part)
            part.add_header(
                'Content-Disposition',
                f'attachment; filename="{Header(os.path.basename(attachment_path), "utf-8").encode()}"; part={part_number}'
            )
            msg.attach(part)
            part_number += 1

# 发送邮件
def send_email(sender_email, sender_password, recipient_email, subject, body, attachment_path):
    try:
        smtp_server = get_smtp_server(sender_email)
        msg = MIMEMultipart()
        msg['From'] = sender_email
        msg['To'] = recipient_email
        msg['Subject'] = Header(subject, 'utf-8').encode()
        msg.attach(MIMEText(body, 'plain'))

        if attachment_path:
            if is_attachment_too_large(attachment_path):
                logging.error("附件大小超出限制")
                raise ValueError("附件大小超出限制 (25MB)")
            attach_file_in_chunks(msg, attachment_path)

        with smtplib.SMTP_SSL(smtp_server, 465, timeout=120) as server:
            server.login(sender_email, sender_password)
            server.sendmail(sender_email, recipient_email, msg.as_string())

        logging.info(f"邮件发送成功: {recipient_email}")
        return True
    except Exception as e:
        logging.error(f"邮件发送失败: {e}")
        return False

# 打开文件选择对话框
def open_file_dialog():
    return filedialog.askopenfilename()

# GUI 界面
def send_email_gui():
    def center_window(window, width, height):
        """窗口居中显示"""
        screen_width = window.winfo_screenwidth()
        screen_height = window.winfo_screenheight()
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2
        window.geometry(f"{width}x{height}+{x}+{y}")

    def toggle_controls(state):
        """启用或禁用控件"""
        for widget in [sender_email_entry, sender_password_entry, recipient_email_entry,
                       subject_entry, body_text, attachment_entry, browse_button,
                       send_button, reset_button]:
            widget.config(state=state)

    def send():
        def task():
            toggle_controls("disabled")
            sender_email = sender_email_entry.get()
            sender_password = sender_password_entry.get()
            recipient_email = recipient_email_entry.get()
            subject = subject_entry.get()
            body = body_text.get("1.0", tk.END).strip()
            attachment_path = attachment_path_var.get()

            if not sender_email or not sender_password or not recipient_email:
                toggle_controls("normal")
                messagebox.showerror("错误", "请填写所有必填项！", parent=root)
                return

            success = send_email(sender_email, sender_password, recipient_email, subject, body, attachment_path)

            toggle_controls("normal")
            if success:
                messagebox.showinfo("成功", "邮件发送成功！", parent=root)
                save_config(sender_email, sender_password, recipient_email)
            else:
                messagebox.showerror("失败", "邮件发送失败，请检查信息！", parent=root)

        threading.Thread(target=task).start()

    def browse_file():
        """选择文件"""
        attachment_path_var.set(open_file_dialog())

    def reset_credentials():
        """重置输入框"""
        sender_email_entry.delete(0, tk.END)
        sender_password_entry.delete(0, tk.END)

    # 加载配置
    config = load_config()

    # 创建主窗口
    root = tk.Tk()
    root.title("邮件发送工具")
    window_width, window_height = 800, 450
    center_window(root, window_width, window_height)
    root.resizable(False, False)

    # 创建 UI 元素
    frame = tk.Frame(root)
    frame.pack(expand=True)

    tk.Label(frame, text="发件人邮箱:", font=("Arial", 12)).grid(row=0, column=0, padx=20, pady=10, sticky="e")
    sender_email_entry = tk.Entry(frame, width=50)
    sender_email_entry.grid(row=0, column=1, padx=20, pady=10, sticky="w")
    sender_email_entry.insert(0, config.get("sender_email", ""))

    tk.Label(frame, text="邮箱密码:", font=("Arial", 12)).grid(row=1, column=0, padx=20, pady=10, sticky="e")
    sender_password_entry = tk.Entry(frame, show="*", width=50)
    sender_password_entry.grid(row=1, column=1, padx=20, pady=10, sticky="w")
    sender_password_entry.insert(0, config.get("sender_password", ""))

    tk.Label(frame, text="收件人邮箱:", font=("Arial", 12)).grid(row=2, column=0, padx=20, pady=10, sticky="e")
    recipient_email_entry = tk.Entry(frame, width=50)
    recipient_email_entry.grid(row=2, column=1, padx=20, pady=10, sticky="w")
    recipient_email_entry.insert(0, config.get("recipient_email", ""))

    tk.Label(frame, text="主题:", font=("Arial", 12)).grid(row=3, column=0, padx=20, pady=10, sticky="e")
    subject_entry = tk.Entry(frame, width=50)
    subject_entry.grid(row=3, column=1, padx=20, pady=10, sticky="w")

    tk.Label(frame, text="正文:", font=("Arial", 12)).grid(row=4, column=0, padx=20, pady=10, sticky="ne")
    body_text = tk.Text(frame, width=50, height=8)
    body_text.grid(row=4, column=1, padx=20, pady=10, sticky="w")

    scrollbar = Scrollbar(frame, command=body_text.yview)
    scrollbar.grid(row=4, column=2, sticky='ns', pady=10)
    body_text.config(yscrollcommand=scrollbar.set)

    tk.Label(frame, text="附件:", font=("Arial", 12)).grid(row=5, column=0, padx=20, pady=10, sticky="e")
    attachment_path_var = tk.StringVar()
    attachment_entry = tk.Entry(frame, textvariable=attachment_path_var, width=40)
    attachment_entry.grid(row=5, column=1, padx=20, pady=10, sticky="w")
    browse_button = tk.Button(frame, text="浏览", command=browse_file, font=("Arial", 10))
    browse_button.grid(row=5, column=2, padx=10, pady=10, sticky="w")

    send_button = tk.Button(frame, text="发送邮件", command=send, bg="green", fg="white", font=("Arial", 12))
    send_button.grid(row=6, column=1, pady=20, sticky="e")
    reset_button = tk.Button(frame, text="重置", command=reset_credentials, bg="red", fg="white", font=("Arial", 12))
    reset_button.grid(row=6, column=1, pady=20, sticky="w")

    root.mainloop()

if __name__ == "__main__":
    send_email_gui()
